/**************************************************************************/
/* Application creates an child process using spawn().                    */
/**************************************************************************/

#include "server.h"


int segment_id;
char* SERVER_ADDR;
struct shmid_ds shmbuffer;
int segment_size;
const int shared_segment_size = 0x6400;


// Gate controller protocol
//
// CLN thread:
//	1. Connects to the remote party
//	2. Sends a message to the remote party
//      3. Receives a message from the remote party
//      4. Closes connection
//
// SRV thread:
//      1. Listens for incoming connections
//      2. Passes incoming connection to WRK thread, then listens again
//
// WRK thread:
//      1. Receives a message from the remote party
//      2. Sends a message to the remote party
//      3. Closes connection
//
// Msg name	Parameter	Parameter example	Thread<->Thread	Description
// ----------------------------------------------------------------------------------------------------------------
//
// REQUEST						CLN -> WRK	Ask remote party for its gate status
// NOTIFY	status		open			CLN -> WRK	Report to remote party status of the local gate
// REGISTER	ip_address	127.0.0.1		CLN -> WRK	Update remote party with a new master ctrl address
// RESET						CLN <-> WRK	Reboot remote party
// ACK							WRK -> CLN	A default response

#define SECS 10 // Local gate status query interval in seconds
#define SRV 0
#define CLN 1
#define WRK 2

extern in_addr_t inet_addr (__const char *__cp) __THROW;

void print_log(int peer, const char *msg);
void start_client();
void start_server();
void start_worker(int sockfd);
int send_msg(int peer, int sockfd, const char *msg);
char *recv_msg(int peer, int sockfd);
void print_msg(const char *s);
bool free_msg(msgTokens *msg);
void parse_msg(msgTokens *msg, const char *s);

/*
int main() {

	msgTokens msg;
	parse_msg(&msg, "REGISTER 4.2.2.2");
	
	int i;
	for (i=0; i<msg.size; i++)
		printf ("Token[%d]: %s\n", i, msg.tokens[i]);
	
	free(&msg);
	
    return 0;
}
*/

int semid;

int main (int argc, char *argv[])
{
    pid_t child_pid;

   /* Allocate a shared memory segment.  */
    segment_id = shmget (IPC_PRIVATE, shared_segment_size,
	    IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
    printf("segment_id = %d\n", segment_id);


    // setup semaphores
    key_t key = IPC_PRIVATE;
    int nsems = 0;
    semid = binary_semaphore_allocation(key, nsems);
    printf("semid = %d\n", semid);
    printf("binary_semaphore_initialize() returned %d\n", binary_semaphore_initialize(semid));

    /* Attach the shared memory segment.  */
    SERVER_ADDR = (char*) shmat (segment_id, 0, 0);
    printf ("shared memory attached at address %p\n", SERVER_ADDR);
    /* Determine the segment's size.  */
    shmctl (segment_id, IPC_STAT, &shmbuffer);
    segment_size = shmbuffer.shm_segsz;
    printf ("segment size: %d\n", segment_size);
    strncpy(SERVER_ADDR, "127.0.0.1", strlen("127.0.0.1") + 1);
    /* Deatch the shared memory segment.  */
    shmdt (SERVER_ADDR);


    /*************************************************/
    /* Process arguments here      */
    /*************************************************/

    /* Split split current process into server and client processes  */
    child_pid = fork ();
    if (child_pid != 0) {
	// First off, we fire up the server
	//
	//

	/* This is the parent process.  */

	start_server();
    } else {
	// Next we start the client
	//
	//

	/* This is the child process.  */

	sleep(5); // Wait for server to come up

	start_client();
    }

    return 0;
}

void start_client() {
    // Do the rest of the code till the user types Ctrl-C
    // with a 10 seconds pause between iterations
    for (;;) {
	int rc;
	int    sockfd;
	struct sockaddr_in   addr;
	char tmp[SIZE];

	/*************************************************/
	/* Create an AF_INET stream socket               */
	/*************************************************/
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
	    print_log(CLN, "socket() failed");
	    exit(-1);
	}

	/*************************************************/
	/* Initialize the socket address structure       */
	/*************************************************/
	memset(&addr, 0, sizeof(addr));
	addr.sin_family      = AF_INET;
	//addr.sin_addr.s_addr = htonl(INADDR_ANY);

	print_log(CLN, "wait() Entry");
	binary_semaphore_wait(semid);

	/* Attach the shared memory segment.  */
	SERVER_ADDR = (char*) shmat (segment_id, 0, 0);
	printf ("shared memory attached at address %p\n", SERVER_ADDR);
	/* Determine the segment's size.  */
	shmctl (segment_id, IPC_STAT, &shmbuffer);
	segment_size = shmbuffer.shm_segsz;
	printf ("segment size: %d\n", segment_size);
	addr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
	sprintf(tmp, "Trying to connect to %s", SERVER_ADDR);
	/* Deatch the shared memory segment.  */
	shmdt (SERVER_ADDR);

	binary_semaphore_post(semid);
	print_log(CLN, "post() Exit");

	addr.sin_port        = htons(SERVER_PORT);

	/*************************************************/
	/* Connect to the server                         */
	/*************************************************/
	print_log(CLN, tmp);
	rc = connect(sockfd,
		(struct sockaddr *)&addr,
		sizeof(struct sockaddr_in));
	if (rc < 0)
	{
	    print_log(CLN, "connect() failed");
	    goto clean_up;
	}
	print_log(CLN, "connected");

	/*************************************************/
	/* Send message to the remote party          */
	/*************************************************/
	send_msg(CLN, sockfd, "REGISTER 4.2.2.2");

	/*************************************************/
	/* Get a message from the remote party      */
	/*************************************************/
	sprintf(tmp, "Message received: %s", recv_msg(CLN, sockfd));
	print_log(CLN, tmp);

	/*************************************************/
	/* Close down the socket                         */
	/*************************************************/
clean_up: close(sockfd);
	  sleep(SECS);
    }
}

void start_server() {
    int rc, on = 1;
    int listen_sd = -1;
    int accept_sd = -1;
    struct sockaddr_in   addr;

    /*************************************************/
    /* Create an AF_INET stream socket to receive    */
    /* incoming connections on                       */
    /*************************************************/
    listen_sd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sd < 0)
    {
	print_log(SRV, "socket() failed");
	exit(-1); // This and subsequent exits shut down the whole program
    }

    /*************************************************/
    /* Allow socket descriptor to be reuseable       */
    /*************************************************/
    rc = setsockopt(listen_sd,
	    SOL_SOCKET,  SO_REUSEADDR,
	    (char *)&on, sizeof(on));
    if (rc < 0)
    {
	print_log(SRV, "setsockopt() failed");
	close(listen_sd);
	exit(-1);
    }

    /*************************************************/
    /* Bind the socket                               */
    /*************************************************/
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_port        = htons(LISTEN_PORT);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    rc = bind(listen_sd,
	    (struct sockaddr *)&addr, sizeof(addr));
    if (rc < 0)
    {
	print_log(SRV, "bind() failed");
	close(listen_sd);
	exit(-1);
    }

    /*************************************************/
    /* Set the listen back log                       */
    /*************************************************/
    rc = listen(listen_sd, 5);
    if (rc < 0)
    {
	print_log(SRV, "listen() failed");
	close(listen_sd);
	exit(-1);
    }

    /*************************************************/
    /* Inform the user that the server is ready      */
    /*************************************************/
    print_log(SRV, "server ready");

    /*************************************************/
    /* Go through the loop once for each connection  */
    /*************************************************/
    for (;;)
    {
	/**********************************************/
	/* Wait for an incoming connection            */
	/**********************************************/
	print_log(SRV, "waiting on accept()");
	accept_sd = accept(listen_sd, NULL, NULL);
	if (accept_sd < 0)
	{
	    print_log(SRV, "accept() failed");
	    close(listen_sd);
	    exit(-1);
	}
	print_log(SRV, "accept() completed successfully");

	/**********************************************/
	/* Create the worker job                      */
	/**********************************************/
	print_log(SRV, "creating worker job");
	pid_t child_pid;

	/* Duplicate this process.  */
	child_pid = fork ();
	if (child_pid != 0) {
	    /* This is the parent process.  */

	    // Do nothing here. Proceed with the next iteration
	    //
	    //

	} else {
	    // Pass open connection to the worker process
	    //
	    //

	    start_worker(accept_sd);
	    exit(0); // Kill worker job
	}

    }

    /*************************************************/
    /* Close down the listen socket                  */
    /*************************************************/
    close(listen_sd);
    /* Deallocate the shared memory segment.  */
    shmctl (segment_id, IPC_RMID, 0);

    printf("binary_semaphore_deallocate() returned %d\n", binary_semaphore_deallocate(semid));
}

void start_worker(int sockfd) {
    char msg[SIZE];
    char tmp[SIZE];
    /*************************************************/
    /* Receive a message from the remote party             */
    /*************************************************/
    print_log(WRK, "wait for client to send us a message");
    strncpy(msg, recv_msg(WRK, sockfd), sizeof(tmp));
    sprintf(tmp, "Message received: %s", msg);
    print_log(WRK, tmp);

    // Parse message
    msgTokens tokens;
    parse_msg(&tokens, msg);

    int i;
    for (i=0; i<tokens.size; i++)
	printf ("Token[%d]: %s\n", i, tokens.tokens[i]);

    if (tokens.size < 1) goto clean_up;

    /*************************************************/
    /* Send a message to the remote party             */
    /*************************************************/
    if (!strncmp("RESET", tokens.tokens[0], strlen(msg))) {
	/*************************************************/
	/* Reboot              */
	/*************************************************/
	print_log(WRK, "Rebooting ...");
	send_msg(WRK, sockfd, "ACK");
    } else if (!strncmp("REQUEST", tokens.tokens[0], strlen(msg))) {
	/*************************************************/
	/* Report local gate status to the client              */
	/*************************************************/
	print_log(WRK, "querying gate status ...");
	send_msg(WRK, sockfd, "NOTIFY open");
    } else if (!strncmp("NOTIFY", tokens.tokens[0], strlen(msg))) {
	/*************************************************/
	/* Process remote client's gate status              */
	/*************************************************/
	print_log(WRK, "Process NOTIFY message, extract status parameter ...");
	send_msg(WRK, sockfd, "ACK");
    } else if (!strncmp("REGISTER", tokens.tokens[0], strlen(msg))) {
	/*************************************************/
	/* Update master ctrl address */
	/*************************************************/
	print_log(WRK, "Process REGISTER message, extract ip_address parameter ...");
	if (tokens.size > 1) {
	    sprintf(tmp, "Updating master ctrl ip_address with %s\n", tokens.tokens[1]);
	    print_log(WRK, tmp);

	    print_log(WRK, "wait() Entry");
	    binary_semaphore_wait(semid);

	    /* Attach the shared memory segment.  */
	    SERVER_ADDR = (char*) shmat (segment_id, 0, 0);
	    printf ("shared memory attached at address %p\n", SERVER_ADDR);
	    /* Determine the segment's size.  */
	    shmctl (segment_id, IPC_STAT, &shmbuffer);
	    segment_size = shmbuffer.shm_segsz;
	    printf ("segment size: %d\n", segment_size);
	    strncpy(SERVER_ADDR, tokens.tokens[1], strlen(tokens.tokens[1]) + 1);
	    /* Deatch the shared memory segment.  */
	    shmdt (SERVER_ADDR);

	    binary_semaphore_post(semid);
	    print_log(WRK, "post() Exit");
	}
	send_msg(WRK, sockfd, "ACK");
    } else { // This branch normally should not be invoked
	print_log(WRK, "Nothing to do");
	send_msg(WRK, sockfd, "ACK");
    }

clean_up: free(&msg);

    /*************************************************/
    /* Close down the incoming connection            */
    /*************************************************/
    close(sockfd);
}

int send_msg(int peer, int sockfd, const char *msg) {
    char tmp[SIZE];
    int    len;

    sprintf(tmp, "send_msg(): Sending message: %s", msg);
    print_log(peer, tmp);

    len = send(sockfd, msg, strlen(msg) + 1, 0);
    if (len != strlen(msg) + 1)
    {
	print_log(CLN, "send_msg(): send() failed");
	close(sockfd);
	exit(-1);
    }
    sprintf(tmp, "send_msg(): %d bytes sent", len);
    print_log(peer, tmp);
    return len;
}

char *recv_msg(int peer, int sockfd) {
    static char   recv_buf[SIZE];
    char tmp[SIZE];
    int    rc;

    rc = recv(sockfd, recv_buf, sizeof(recv_buf), 0);
    if (rc <= 0)
    {
	print_log(peer, "recv_msg(): recv() failed");
	close(sockfd);
	exit(-1);
    }

    sprintf(tmp, "recv_msg(): Message received: %s", recv_buf);
    print_log(peer, tmp);
    sprintf(tmp, "recv_msg(): %d bytes received", rc);
    print_log(peer, tmp);
    return recv_buf;
}

void parse_msg(msgTokens *msg, const char *s) {
    // Copy string to a buffer, cause strtok requires it to be writable
    int len = strlen(s);
    char buf[len + 1];
    strncpy(buf, s, len);
    buf[len] = '\0';

	int count = 0;
	
/** Determine how many tokens we've got **/
	{
		char * pch;
		pch = strtok (buf," ");		
		while (pch != NULL)
		{
			count++;
			pch = strtok (NULL, " ");
		}
	}
	
	/** Allocate and copy message into structure **/
	msg->size = count;
	msg->tokens = calloc(msg->size, sizeof(char*));
	
	{
		//Reset the buffer
		strncpy(buf, s, len);
    	buf[len] = '\0';
		char * pch;
		pch = strtok (buf," ");
		int i;
		
		for (i=0; i<msg->size; i++)
		{
			msg->tokens[i] = calloc (1, strlen(pch));
			strncpy(msg->tokens[i], pch, strlen(pch));		
			pch = strtok (NULL, " ");
			
		}
	
	}	
	
	
}

bool free_msg (msgTokens *msg) {
	
	int i;
	
	//Free the strings
	for (i=0; i<msg->size; i++)
	{
		
		free (msg->tokens[i]);
	}
	
	free (msg->tokens);
	
	return true;
}


void print_log(int peer, const char *msg) {
    if (SRV == peer) {
	printf("SRV [%d] - %s\n", getpid(), msg);
    } else if (CLN == peer) {
	printf("CLN [%d] - %s\n", getpid(), msg);
    } else if (WRK == peer) {
	printf("WRK [%d] - %s\n", getpid(), msg);
    } else {
	printf("UNKNOWN [%d] - %s\n", getpid(), msg);
    }
}

